package mtm.ui.panel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Calendar;
import java.util.Locale;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import mtm.constants.Constants;
import mtm.date.MtmDate;
import mtm.date.MtmMonth;
import mtm.ui.MainFrame;
import mtm.ui.job.JobFrame;
import mtm.ui.job.RepeatJobFrame;

import com.toedter.calendar.JMonthChooser;
import com.toedter.calendar.JYearChooser;

public class CalendarPanel extends JPanel {
	
	private static CalendarPanel instance = null;
	
	private static Font mouseHoveredFont = new Font(Font.DIALOG, Font.BOLD, 16);
	private static Font normalFont = new Font(Font.DIALOG, Font.PLAIN, 12);
	
	private JMonthChooser monthChooser;
	private JYearChooser yearChooser;
	private JPanel labelsPanel;
	
	private DateLabel[] lblDays = new DateLabel[7];
	private DateLabel[] lblDates = new DateLabel[42];
	private MtmMonth month;
	private int currentDate = Calendar.getInstance().get(Calendar.DATE);
	
	private JButton btnCurrentWeek;
	
	private boolean mouseDragged;
	private boolean mouseReleased;
	private int startDate = 0;
	private int endDate = 0;
	private int firstDay;
	
	private Color sundayForeground = Color.BLUE;
	private Color currentDateColor = Color.RED;
	
	
	/**
	 * Singleton instance
	 */
	public static CalendarPanel getInstance() {
		return instance == null ? new CalendarPanel(): instance;
	}
	
	
	/**
	 * Constructor
	 */
	private CalendarPanel() {
		super();
		instance = this;
		
		// set hand cursor
		setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		
		// month chooser
		monthChooser = new JMonthChooser();
		monthChooser.setLocale(new Locale("vi"));
		monthChooser.addPropertyChangeListener(new PropertyChangeListener() {
			
			@Override
			public void propertyChange(PropertyChangeEvent event) {
				try {
					int newMonth = (Integer) (event.getNewValue());
					month.setMonth(1 + newMonth);
					paintLabels();
					updateJobView();
				} catch (Exception ex) {}
			}
		});
		
		// year chooser
		yearChooser = new JYearChooser();
		yearChooser.addPropertyChangeListener(new PropertyChangeListener() {
			
			@Override
			public void propertyChange(PropertyChangeEvent event) {
				try {
					int year = (Integer) event.getNewValue();
					if (year < Constants.START_YEAR || year > Constants.END_YEAR) {
						return;
					}
					month.setYear(year);
					paintLabels();
					updateJobView();
				} catch (Exception ex) {}
			}
		});
		
		// labels panel
		labelsPanel = new JPanel();
		
		// set up month
		this.month = new MtmMonth();
		
		// day labels
		for (int i = 0; i < 7; ++i) {
			lblDays[i] = new DateLabel(Constants.SIMPLIFIED_DAYS[i]);
		}
		lblDays[0].setForeground(sundayForeground);
		
		
		for (int i = 0; i < lblDates.length; ++i) {
			lblDates[i] = new DateLabel();
		}
		
		// current week button, jump to current week 
		btnCurrentWeek = new JButton("Tuần hiện tại");
		btnCurrentWeek.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				goToCurrentDate();
			}
		});
		
		// paint labels
		paintLabels();
		
		// layout labels
		setLabelsLayout();
		
		// setup main panel
		setLayout(new BorderLayout());
		
		JPanel chooserPanel = new JPanel();
		chooserPanel.add(monthChooser);
		chooserPanel.add(yearChooser);
		
		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
		buttonPanel.add(btnCurrentWeek);
		
		add(chooserPanel, BorderLayout.NORTH);
		add(labelsPanel, BorderLayout.CENTER);
		add(buttonPanel, BorderLayout.SOUTH);
	}
	
	
	/**
	 * Display current on calendar
	 */
	private void goToCurrentDate() {
		MtmDate currentDate = new MtmDate();
		monthChooser.setMonth(currentDate.getMonth() - 1);
		yearChooser.setYear(currentDate.getYear());
		
		// display jobs in current week
		MainFrame.getInstance().setJobView(Constants.WEEK_VIEW);
		WeekPanel.getInstance().displayJobsInWeek(MtmDate.getFirstDayOfCurrentWeek());
	}


	/**
	 * Setup components in this panel and layout
	 */
	private void paintLabels() {
		// date labels
		firstDay = month.getFirstDay();
		int numOfDays = month.getNumberOfDays();
		for (DateLabel lbl : lblDates) {
			lbl.setText("");
		}
		for (int i = 1; i <= numOfDays; ++i) {
			lblDates[i + firstDay - 2].setText(i + "");
		}
		
		setCurrentDate();
		setSundayForeground(sundayForeground);
		
		labelsPanel.revalidate();
	}
	
	
	/**
	 * Set layout for labels
	 */
	private void setLabelsLayout() {
		labelsPanel.setLayout(new GridLayout(7, 7, 3, 3));
		
		for (JLabel lbl : lblDays) {
			labelsPanel.add(lbl);
		}
		
		for (JLabel lbl: lblDates) {
			labelsPanel.add(lbl);
		}
	}
	
	
	/**
	 * Set foreground color of Sundays
	 * @param color
	 */
	private void setSundayForeground(Color color) {
		sundayForeground = color;
		lblDays[0].setForeground(sundayForeground);
		for (int i = 0; i < 42; i += 7) {
			lblDates[i].setForeground(sundayForeground);
		}
	}
	
	
	/**
	 * Set foreground and font size for current date
	 */
	private void setCurrentDate() {
		if (month.isCurrentMonth()) {
			lblDates[currentDate + firstDay - 2].setForeground(currentDateColor);
		} else {
			for (DateLabel lbl : lblDates) {
				lbl.setForeground(null);
			}
		}
	}
	
	
	/**
	 * 
	 * @param color foreground color of current date
	 */
	private void setCurrentDateColor(Color color) {
		currentDateColor = color;
		setCurrentDate();
	}
	
	
	/**
	 * The selected month 
	 * @return
	 */
	public MtmMonth getSelectedMonth() {
		return month;
	}
	
	
	/**
	 * Job view means WeekPanel and MonthPanel, this function update UI of the
	 * selected panel in tab of MainFrame
	 */
	private void updateJobView() {
		if (MainFrame.getInstance().isWeekView()) {
			MtmDate firstDayOfMonth = new MtmDate(month.getYear(), month.getMonth(), 1);
			WeekPanel.getInstance().displayJobsInWeek(MtmDate.getFirstDayOfWeek(firstDayOfMonth));
		} else {
			MonthPanel.getInstance().displayJobsInMonth(month);
		}
	}
	
	
	/**
	 * Label to display date in Calendar Panel
	 */
	public class DateLabel extends JLabel {
		
		private Color mouseHoveredColor = new Color(0x99, 0xFF, 0xFF);
		private Color selectedBackground = new Color(0x33, 0xFF, 0x99);
		private Color selectedForeground = new Color(0x00, 0x66, 0x33);
		
		private boolean isSelected;
		private boolean isControlDown = false;
		
		/**
		 * Constructor
		 */
		public DateLabel() {
			super();
			setOpaque(true);
			
			// center the text
			setVerticalTextPosition(JLabel.CENTER);
			setHorizontalAlignment(JLabel.CENTER);
			
			// listener
			addMouseListener(new MouseAdapter() {
				
				@Override
				public void mouseEntered(MouseEvent e) {
					// hover date label
					int parsedDate = -1;
					try {
						parsedDate = Integer.parseInt(getText());
					} catch (NumberFormatException ex) {
					}
					if (parsedDate != -1) {
						setBackground(mouseHoveredColor);
						
						int firstDateOfWeek = 7 * (parsedDate / 7);
						// set larger font for dates in a week
						if (firstDateOfWeek != 0) {
							for (int i = firstDateOfWeek; i < firstDateOfWeek + 7; ++i) {
								lblDates[i].setFont(mouseHoveredFont);
							}
						} else {
							for (int i = 1; i < (firstDateOfWeek + 7); ++i) {
								lblDates[i].setFont(mouseHoveredFont);
							}
						}
					}
					
					if (mouseDragged) {
						try {
							endDate = Integer.parseInt(getText());
						} catch (NumberFormatException ex) {}
						selectDates(true);
					}
					
					// update WeekPanel if it is currently selected
					if (MainFrame.getInstance().isWeekView()) {
						if (parsedDate != -1) {
							MtmDate dateInWeek = new MtmDate(month.getYear(), month.getMonth(), parsedDate);
							WeekPanel.getInstance().displayJobsInWeek(MtmDate.getFirstDayOfWeek(dateInWeek));
						}
					}
				}
				
				@Override
				public void mouseExited(MouseEvent e) {
					if (isSelected) {
						setBackground(selectedBackground);
					}
					else {
						setBackground(null);
						
						int parsedDate = -1;
						try {
							parsedDate = Integer.parseInt(getText());
						} catch (NumberFormatException ex) {
						}
						// set normal font 
						if (parsedDate != -1) {
							int firstDateOfWeek = 7 * (parsedDate / 7);
							if (firstDateOfWeek != 0) {
								for (int i = firstDateOfWeek; i < firstDateOfWeek + 7; ++i) {
									lblDates[i].setFont(normalFont);
								}
							} else {
								for (int i = 1; i < (firstDateOfWeek + 7); ++i) {
									lblDates[i].setFont(normalFont);
								}
							}
						}
					}
				}
				
				@Override
				public void mousePressed(MouseEvent e) {
					
					// ctrl + click to setup repeat job
					if (e.isControlDown()) {
						isControlDown = true;
						try {
							startDate = Integer.parseInt(getText());
						} catch (NumberFormatException ex) {
							mouseDragged = false;
						}
						processSelectedDates();
						return;
					}
					
					mouseDragged = true;
					if (mouseReleased) {
						selectDates(false);
					}
					
					try {
						startDate = Integer.parseInt(getText());
					} catch (NumberFormatException ex) {
						mouseDragged = false;
					}
					
					mouseReleased = false;
				}
				
				@Override
				public void mouseReleased(MouseEvent e) {
					if (mouseDragged && !isControlDown) {
						selectDates(true);
						processSelectedDates();
						mouseDragged = false;
						mouseReleased = true;
						
						// set font of all date labels to normal 
						for (DateLabel lbl : lblDates) {
							lbl.setFont(normalFont);
							lbl.setSelected(false);
						}
					} 
				}
				
			});
		}
		
		
		/**
		 * Select dates in calendar panel
		 * @param select
		 */
		private void selectDates(boolean select) {
			if (startDate > 0 & endDate > 0) {
				if (startDate >= endDate) {
					for (int i = endDate; i <= startDate; ++i) {
						lblDates[i + firstDay - 2].setSelected(select);
					}
				} else {
					for (int i = startDate; i <= endDate; i++) {
						lblDates[i + firstDay - 2].setSelected(select);
					}
				}
			}
			else if (endDate == 0) {
				lblDates[startDate + firstDay - 2].setSelected(select);
			}
			else if (startDate == 0) {
				lblDates[endDate + firstDay - 2].setSelected(select);
			}
			
			if (!select) {
				startDate = endDate = 0;
			}
		}
		
		
		/**
		 * Process selected dates
		 */
		private void processSelectedDates() {
			if (endDate > 0 && endDate < startDate) {
				endDate ^= startDate;
				startDate ^= endDate;
				endDate ^= startDate;
			}
			
			// selecte only one date
			if (endDate == 0) {
				if (!isControlDown) {
					new JobFrame(new MtmDate(month.getYear(), month.getMonth(), startDate));
				} else {
					new RepeatJobFrame(
							new MtmDate(month.getYear(), month.getMonth(), startDate), 
							new MtmDate(month.getYear(), month.getMonth(), startDate));
					
					isControlDown = false;
				}
			}
			// select some dates
			else {
				new RepeatJobFrame(
						new MtmDate(month.getYear(), month.getMonth(), startDate), 
						new MtmDate(month.getYear(), month.getMonth(), endDate));
			}
		}
		
		
		/**
		 * Constructor
		 * @param text the initial text of label
		 */
		public DateLabel(String text) {
			this();
			setText(text);
		}
		
		
		/**
		 * Set this label in selected state
		 */
		public void setSelected(boolean selected) {
			this.isSelected = selected;
			if (selected) {
				setForeground(selectedForeground);
				setBackground(selectedBackground);
			}
			else {
				setBackground(null);
				try {
					if ( ( Integer.parseInt(getText()) + firstDay - 2  ) % 7 == 0) {
						setForeground(sundayForeground);
					}
					else {
						setForeground(null);
					}
				} catch (NumberFormatException e) {
					setForeground(null);
				}
				
				setCurrentDate();
			}
		}
	}
}
